搭建 vite + vue3 + ts + pinia 项目框架 | 您所在的位置:网站首页 › vue-element-admin vue3 › 搭建 vite + vue3 + ts + pinia 项目框架 |
一、创建项目
1. 安装vite
npm i vite -g
复制代码
2. 创建项目
一步创建
# npm 6.x
npm create vite@latest my-vue-app --template vue-ts
# npm 7+, extra double-dash is needed:
npm create vite@latest my-vue-app -- --template vue-ts
# yarn
yarn create vite my-vue-app --template vue-ts
# pnpm
pnpm create vite my-vue-app --template vue-ts
复制代码
配置创建
npm init vue@latest
复制代码
如果安装依赖后运行 npm run dev 报以下错误 解决方法: 更新node版本 nodejs.org/zh-cn/ 二、项目基本配置 1. 项目icon在 public目录 下,添加一个 favicon.icon 图片 2. 项目标题在 index.html 文件的 title标签 中配置 3. 配置 tsconfig.json能让 代码提示 变得更加友好 { "compilerOptions": { // 允许从没有设置默认导出的模块中默认导入。这并不影响代码的输出,仅为了类型检查。 "allowSyntheticDefaultImports": true, // 解析非相对模块名的基准目录 "baseUrl": ".", // 模块加载兼容模式,可以是呀import from语法导入commonJS模块 "esModuleInterop": true, // 从 tslib 导入辅助工具函数(比如 __extends, __rest等) "importHelpers": true, // 指定生成哪个模块系统代码 "module": "esnext", // 决定如何处理模块。 "moduleResolution": "node", // 启用所有严格类型检查选项。 // 启用 --strict相当于启用 --noImplicitAny, --noImplicitThis, --alwaysStrict, // --strictNullChecks和 --strictFunctionTypes和--strictPropertyInitialization。 "strict": true, "noImplicitAny": false, //关闭implicitly has an 'any' type // 支持jsx语法 "jsx": "preserve", // 生成相应的 .map文件。 "sourceMap": true, // 忽略所有的声明文件( *.d.ts)的类型检查。 "skipLibCheck": true, // 指定ECMAScript目标版本 "target": "esnext", // 要包含的类型声明文件名列表 "types": [ "node" ], "typeRoots": [ "../node_modules/@types" ], // isolatedModules 设置为 true 时,如果某个 ts 文件中没有一个import or export 时,ts 则认为这个模块不是一个 ES Module 模块,它被认为是一个全局的脚本, "isolatedModules": true, // 模块名到基于 baseUrl的路径映射的列表。 "paths": { "@/*": [ "src/*" ] }, "vueCompilerOptions": { "experimentalDisableTemplateSupport": true //去掉volar下el标签红色波浪线问题 }, // 编译过程中需要引入的库文件的列表。 "lib": [ "ESNext", "DOM", "DOM.Iterable", "ScriptHost" ] }, // 解析的文件 "include": [ "env.d.ts", "src/**/*", "src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue", "src/*.js", "src/**/*.jsx" ], "exclude": [ "node_modules" ], "references": [ { "path": "./tsconfig.node.json" } ] } 复制代码 4. 设置 .prettierrc.json 文件eslint 配置格式化选项说明 // 1.一行代码的最大字符数,默认是80(printWidth: ) printWidth: 80, // 2.tab宽度为2空格(tabWidth: ) tabWidth: 2, // 3.是否使用tab来缩进,我们使用空格(useTabs: ) useTabs: false, // 4.结尾是否添加分号,false的情况下只会在一些导致ASI错误的其工况下在开头加分号,我选择无分号结尾的风格(semi: ) semi: false, // 5.使用单引号(singleQuote: ) singleQuote: true, // 6.object对象中key值是否加引号(quoteProps: "")as-needed只有在需求要的情况下加引号,consistent是有一个需要引号就统一加,preserve是保留用户输入的引号 quoteProps: 'as-needed', // 7.在jsx文件中的引号需要单独设置(jsxSingleQuote: ) jsxSingleQuote: false, // 8.尾部逗号设置,es5是尾部逗号兼容es5,none就是没有尾部逗号,all是指所有可能的情况,需要node8和es2017以上的环境。(trailingComma: "") trailingComma: 'es5', // 9.object对象里面的key和value值和括号间的空格(bracketSpacing: ) bracketSpacing: true, // 10.jsx标签多行属性写法时,尖括号是否另起一行(jsxBracketSameLine: ) jsxBracketSameLine: false, // 11.箭头函数单个参数的情况是否省略括号,默认always是总是带括号(arrowParens: "") arrowParens: 'always', // 12.range是format执行的范围,可以选执行一个文件的一部分,默认的设置是整个文件(rangeStart: rangeEnd: ) rangeStart: 0, rangeEnd: Infinity, // 18. vue script和style标签中是否缩进,开启可能会破坏编辑器的代码折叠 vueIndentScriptAndStyle: false, // 19. endOfLine: "" 行尾换行符,默认是lf, endOfLine: 'lf', // 20.embeddedLanguageFormatting: "off",默认是auto,控制被引号包裹的代码是否进行格式化 embeddedLanguageFormatting: 'off', 复制代码 { "singleQuote": true, "tabWidth": 4, "semi": false, } 复制代码 5. 设置 vite.config.ts 文件安装 gzip 和 mock 依赖 npm i vite-plugin-compression vite-plugin-mock -D 复制代码 import { defineConfig } from 'vite' import vue from '@vitejs/plugin-vue' import vueJsx from '@vitejs/plugin-vue-jsx' import path from 'path' // gzip插件 import viteCompression from 'vite-plugin-compression' // mock插件 import { viteMockServe } from 'vite-plugin-mock' const resolve = (dir) => path.resolve(__dirname, dir) export default defineConfig({ base: './', //打包路径 publicDir: resolve('public'), //静态资源服务的文件夹 plugins: [ vue(), vueJsx(), // gzip压缩 生产环境生成 .gz 文件 viteCompression({ verbose: true, disable: false, threshold: 10240, algorithm: 'gzip', ext: '.gz', }), //mock viteMockServe({ mockPath: './mocks', // 解析,路径可根据实际变动 localEnabled: true, // 此处可以手动设置为true,也可以根据官方文档格式 }), ], // 配置别名 resolve: { alias: { '@': resolve('src'), }, // 导入时想要省略的扩展名列表 extensions: ['.mjs', '.js', '.ts', '.jsx', '.tsx', '.json', '.vue'], }, css: { // css预处理器 preprocessorOptions: { scss: { additionalData: '@import "@/assets/styles/common.scss";@import "@/assets/styles/reset.scss";', }, }, }, //启动服务配置 server: { host: '0.0.0.0', port: 8000, open: true, // 自动在浏览器打开 proxy: {}, }, // 打包配置 build: { //浏览器兼容性 "esnext"|"modules" target: 'modules', //指定输出路径 outDir: 'build', //生成静态资源的存放路径 assetsDir: 'assets', //启用/禁用 CSS 代码拆分 cssCodeSplit: true, sourcemap: false, assetsInlineLimit: 10240, // 打包环境移除console.log, debugger minify: 'terser', terserOptions: { compress: { drop_console: true, drop_debugger: true, }, }, rollupOptions: { input: { main: resolve('index.html'), }, output: { entryFileNames: `js/[name]-[hash].js`, chunkFileNames: `js/[name]-[hash].js`, assetFileNames: `[ext]/[name]-[hash].[ext]`, }, }, }, }) 复制代码 三、项目目录结构划分 assets 存放 => 静态资源 css => 样式重置 img => 图片文件 font => 字体文件 components 存放 => 公共组件 hooks 存放 => 公共常用的hook mock 存放 => 模拟接口数据 router 存放 => 路由管理 service 存放 => 接口请求 stores 存放 => 状态管理 utils 存放 => 插件、第三方插件 views 存放 => 视图、页面 四、css 样式重置自定义的css公共文件放置在assets中的css文件中即可 1. normalize.css01 - 安装 npm i normalize.css 复制代码02 - 引入 // 在 main.js 中引入 import 'normalize.css'; 复制代码 2. reset.css01 - 代码 html, body, div, span, applet, object, iframe, h1, h2, h3, h4, h5, h6, p, blockquote, pre, a, abbr, acronym, address, big, cite, code, del, dfn, em, font, img, ins, kbd, q, s, samp, small, strike, strong, sub, sup, tt, var, b, u, i, center, dl, dt, dd, ol, ul, li, fieldset, form, label, legend, caption { margin: 0; padding: 0; border: 0; outline: 0; font-size: 100%; vertical-align: baseline; background: transparent; } table, tbody, tfoot, thead, tr, th, td { margin: 0; padding: 0; outline: 0; font-size: 100%; vertical-align: baseline; background: transparent; } button, input, textarea { margin: 0; padding: 0; } /* form elements 表单元素 */ body, button, input, select, textarea { font: normal 12px/1.5 '\5FAE\8F6F\96C5\9ED1', tahoma, arial; } /*设置的字体,行高*/ h1, h2, h3, h4, h5, h6, th { font-size: 100%; font-weight: normal; } /*重置标题*/ address, cite, dfn, var { font-style: normal; } /* 将斜体扶正 */ code, kbd, pre, samp { font-family: 'courier new', courier, monospace; } /* 统一等宽字体 */ small { font-size: 12px; } /* 小于 12px 的中文很难阅读,让 small 正常化 */ ul, ol { list-style: none; } /* 重置列表元素 */ button, input[type="submit"], input[type="button"] { cursor: pointer; } input[type="radio"], input[type="checkbox"], input[type="submit"], input[type="reset"] { vertical-align: middle; cursor: pointer; border: none; } /** 重置文本格式元素 **/ a { text-decoration: none; } a:hover { text-decoration: underline; } a:focus { outline: 0; } sup { vertical-align: text-top; } /* 重置,减少对行高的影响 */ sub { vertical-align: text-bottom; } /** 重置表单元素 **/ legend { color: #000; } /* for ie6 */ fieldset, img { border: 0; } /* img 搭车:让链接里的 img 无边框 */ button, input, select, textarea { background: transparent; font-size: 100%; outline: 0; } /* 使得表单元素在 ie 下能继承字体大小 */ /* 注:optgroup 无法扶正 */ table { border-collapse: collapse; border-spacing: 0; } td, th { vertical-align: middle; } /** 重置表格元素 **/ /* 重置 HTML5 元素 */ article, aside, details, figcaption, figure, footer, header, hgroup, menu, nav, section, summary, time, mark, audio, video { display: block; margin: 0; padding: 0; } /*回复标签重置*/ blockquote, q { quotes: none; } blockquote:before, blockquote:after, q:before, q:after { content: ''; display: none; } 复制代码02 - 引入 // 在 main.js 中引入 import './assets/css/reset.css'; 复制代码 3. common.css01 - 代码 // 清除浮动 .clearfix { *zoom: 1; } ...... 复制代码02 - 引入 // 在 main.js 中引入 import './assets/css/common.css'; 复制代码 五、vue-router 路由配置一步创建需要安装依赖、配置路由, 引入mian.ts, 配置创建则已自动生成 1. 安装 npm i vue-router 复制代码 2. 配置 // 1. 导入 import { createRouter, createWebHashHistory } from 'vue-router'; // 2. 创建路由对象 const router = createRouter({ history: createWebHashHistory(), routes: [ { path: '/', redirect: '/home' }, { path: '/home', component: () => import('xxx/home.vue') } ] }); // 3. 导出 export default router; 复制代码 3. 引入 // main.js import { createApp } from 'vue'; import App from './App.vue'; // 1. 导入 import router from './router'; import 'normalize.css'; import './assets/css/reset.css'; import './assets/css/common.css'; // 2. 使用 createApp(App).use(router).mount('#app'); 复制代码 4. 使用在该用的地方加上 六、pinia 状态管理一步创建需要安装依赖、配置路由, 引入mian.ts, 配置创建则已自动生成 1. 安装 npm i pinia 复制代码 2. 引入 // main.js import { createApp } from 'vue'; import { createPinia } from "pinia"; import App from './App.vue'; // 1. 导入 import router from './router'; import 'normalize.css'; import './assets/css/reset.css'; import './assets/css/common.css'; // 2. 使用 createApp(App).use(createPinia()).use(router).mount('#app'); 复制代码 3. 模块 // 1. 导入 import { defineStore } from 'pinia'; // 2. 使用 const useDemoStore = defineStore('demoStore', { state: () => ({ arrList: [] }), actions: {}, getters: {} }); // 3. 导出 export default useDemoStore; 复制代码 七、集成 Axios HTTP 工具 安装依赖 npm i axios 复制代码 请求配置在 utils 目录下创建 request.ts 文件,配置好适合自己业务的请求拦截和响应拦截 import axios, { AxiosRequestConfig, Method } from 'axios'; // 创建请求实例 const instance = axios.create({ baseURL: '/api', // 指定请求超时的毫秒数 timeout: 10000, // 表示跨域请求时是否需要使用凭证 withCredentials: false, }); // 设置请求头 instance.defaults.headers.post['Content-Type'] = 'application/json;'; instance.defaults.headers.put['Content-Type'] = 'application/x-www-form-urlencoded'; // instance.defaults.headers.put['Content-Type'] = 'application/json'; // 取消重复请求 const pending = []; // 定义接口 interface PendingType { url?: string; method?: Method; params: any; data: any; cancel: any; } // 移除重复请求 const removePending = (config: AxiosRequestConfig) => { for (const key in pending) { const item: number = +key; const list: PendingType = pending[key]; // 当前请求在数组中存在时执行函数体 if (list.url === config.url && listhod === config.method && JSON.stringify(list.params) === JSON.stringify(config.params) && JSON.stringify(list.data) === JSON.stringify(config.data)) { // 执行取消操作 list.cancel('操作太频繁,请稍后再试'); // 从数组中移除记录 pending.splice(item, 1); } } }; // 请求拦截器(发起请求之前的拦截) instance.interceptors.request.use( (config): AxiosRequestConfig => { removePending(config); config.cancelToken = new axios.CancelToken(c => { pending.push({ url: config.url, method: config.method, params: config.params, data: config.data, cancel: c }); }); /** * 在这里一般会携带前台的参数发送给后台,比如下面这段代码: * const token = getToken() * if (token) { * config.headers.token = token * } */ return config; }, (error) => { return Promise.reject(error); }, ); // 响应拦截器(获取到响应时的拦截) instance.interceptors.response.use( (response) => { removePending(response.config); /** * 根据你的项目实际情况来对 response 和 error 做处理 * 这里对 response 和 error 不做任何处理,直接返回 */ return response; }, (error) => { return Promise.reject(error); }, ); interface ResType { code: number; data?: T; msg?: string; message?: string; err?: string; } interface Http { post(url: string, data?: unknown, params?: unknown,): Promise; get(url: string, params?: unknown): Promise; put(url: string, data?: unknown, params?: any): Promise; _delete(url: string, params?: unknown): Promise; } // 导出常用函数 const http: Http = { post(url, data, params) { return new Promise((resolve, reject) => { instance .post(url, JSON.stringify(data), params) .then((res) => { resolve(res.data); }) .catch((err) => { reject(err.data); }); }); }, get(url, params) { return new Promise((resolve, reject) => { instance .get(url, { params }) .then((res) => { resolve(res.data); }) .catch((err) => { reject(err.data); }); }); }, put(url, data, params) { return new Promise((resolve, reject) => { instance .put(url, data, params) .then((res) => { resolve(res.data); }) .catch((err) => { reject(err.data); }); }); }, _delete(url, params) { return new Promise((resolve, reject) => { instance .delete(url, params) .then((res) => { resolve(res.data); }) .catch((err) => { reject(err.data); }); }); } } export default http; 复制代码之后在 api 文件夹中以业务模型对接口进行拆分,举个例子,将所有跟用户相关接口封装在 User 类中,此类称作用户模型。 在 User 类中比如有登录、注册、获取用户信息等方法,如果有业务逻辑变动,只需要修改相关方法即可。 import { post } from '@/utils/request'; export default class User { /** * 登录 * @param {String} username 用户名 * @param {String} password 密码 * @returns */ static async login(username: string, password: string) { return post('/login', { username, password, }); } } 复制代码把每个业务模型独立成一个 js 文件,声明一个类通过其属性和方法来实现这个模型相关的数据获取,这样可以大大提升代码的可读性与可维护性。 模拟演示在需要使用接口的地方,引入对应的业务模型文件,参考如下 import User from '@/api/user'; import { ref } from 'vue' const username = ref('') const password = ref('') const login = async () => { const res = await User.login(username.value, password.value); console.log(res); }, 复制代码 八、使用scss, 并定义全局scss变量首先我们先安装sass和sass-loader: npm i sass sass-loader -D 复制代码然后我们需要在vite.config.ts中配置css预处理器 export default defineConfig({ css: { preprocessorOptions: { scss: { additionalData: '@import "@/assets/styles/global.scss";@import "@/assets/styles/reset.scss";', }, } } }) 复制代码我们这里默认加载global.scss中的样式,那么我们就需要创建一个这样的文件: // src/assets/style/global.scss $primary-color: #5878e2; // 主题色 复制代码最后在main.ts中引入即可: import "./assets/style/global.scss"; 复制代码然后在组件中使用时,就可以直接使用: import {GlobalStore} from '@/store' const global = GlobalStore(); {{global.token}} div { color: $primary-color; // 主题色 } 复制代码样式穿透 在 Vue3 中,改变了以往样式穿透的语法,如果继续使用 ::v-deep、/deep/、>>> 等语法的话,会出现一个警告,下面是新的语法: /* 深度选择器 */ :deep(selector) { /* ... */ } /* 插槽选择器 */ :slotted(selector) { /* ... */ } /* 全局选择器 */ :global(selector) { /* ... */ } 复制代码 |
CopyRight 2018-2019 实验室设备网 版权所有 |